package practise.mulitThreads.EventSystem.impl;

import practise.mulitThreads.EventSystem.ContainerOperator;
import practise.mulitThreads.EventSystem.EventContainer;
import practise.mulitThreads.EventSystem.entity.Event;

import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by angela on 7/27/2014.
 */
public class ContainerImpl extends AbstractQueue<Event> implements EventContainer {
    private ReentrantLock getLock = new ReentrantLock();
    private ReentrantLock putLock = new ReentrantLock();
    private Condition emptyWaiter = getLock.newCondition();
    private Condition fullWaiter = putLock.newCondition();
    private AtomicInteger counter ;
    private Integer sizeLimit = 0;
    private List<ContainerOperator> clientList = new ArrayList<>();
    private List<ContainerOperator> servers = new ArrayList<ContainerOperator>();
    private List<Event> events = new ArrayList<>();
    private int serverLimit = 5;
    private int clientLimit =10;
    public ContainerImpl(Integer size){
        counter = new AtomicInteger(0);
        this.sizeLimit = size;
    }
    @Override
    public Iterator<Event> iterator() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int size() {
        return counter.get();
    }

    @Override
    public boolean offer(Event event) {
        int c = 0;
        try {
            putLock.lock();
            c=counter.get();
            if(c>=sizeLimit){
                waitWhenFull();
            }
            if(counter.incrementAndGet()+1<sizeLimit){
                notifyNotFull();
            }
            events.add(event);
        }
        finally {
            putLock.unlock();
        }
        if(c==0)
            notifyNotEmpty();
        return false;
    }

    @Override
    public Event poll() {
        int c=counter.get();
        Event event =null;
        try{
            getLock.lock();
            c = counter.get();
            if(c==0){
                waitWhenEmpty();
            }
            if(counter.decrementAndGet()>1){
                notifyNotEmpty();
            }
            event = events.remove(0);
        }
        finally {
            getLock.unlock();
        }
        if(c==sizeLimit){
            notifyNotFull();
        }
        return event;
    }

    @Override
    public Event peek() {
        try{
            getLock.lock();
            if(events.isEmpty())
                return null;
            return events.get(0);
        }
        finally {
            getLock.unlock();
        }
    }

    private void waitWhenEmpty(){
        System.out.println("Container is empty with size :" + events.size());

        try {
            getLock.lock();
            emptyWaiter.await();
        } catch (InterruptedException e) {
//            e.printStackTrace();
        }
        finally {
            getLock.unlock();
        }
    }

    private void notifyNotEmpty(){
        System.out.println("Container is not empty with size :" + events.size());
        try {
            getLock.lock();
            emptyWaiter.signal();
        }
        finally {
            getLock.unlock();
        }

    }

    private void waitWhenFull(){
        System.out.println("Container is full with size :" + events.size());
        try{
            putLock.lock();
            fullWaiter.await();
        } catch (InterruptedException e) {
//            e.printStackTrace();
        } finally {
            putLock.unlock();
        }

    }

    private void notifyNotFull(){
        System.out.println("Container is not full with size :" + events.size());
        try {
            putLock.lock();
            fullWaiter.signal();
        }
        finally {
            putLock.unlock();
        }
    }

    @Override
    public void addProducer(ContainerOperator containerOperator) {
        if(getServers().size()==serverLimit){
            throw new IllegalArgumentException();
        }
        getServers().add(containerOperator);
        containerOperator.register(this);
    }

    @Override
    public void addConsumer(ContainerOperator containerOperator) {
        if(getServers().size()==clientLimit){
            throw new IllegalArgumentException();
        }
        getClientList().add(containerOperator);
        containerOperator.register(this);
    }

    @Override
    public void setProducerLimitation(int size) {
        serverLimit = size;
    }

    @Override
    public void setConsumerLimitation(int size) {
        clientLimit = size;
    }

    public List<ContainerOperator> getClientList() {
        return clientList;
    }

    public List<ContainerOperator> getServers() {
        return servers;
    }
}
